/******************************************************************************/
/* File: tcpSocket.cpp                                                       */
/******************************************************************************/
/* This source code file contains the source code for all the C++ classes     */
/* within the socketcc library that implement TCP Socket functionality.       */
/******************************************************************************/

/******************************************************************************/
/* Include class header file.                                                 */
/******************************************************************************/
#include "socketClass.h"

/******************************************************************************/
/* class TCPSocket.                                                           */
/******************************************************************************/
/* TCPSocket Constructor.                                                     */
/*                                                                            */
/* The default socket type is IPv4.  We call the base class constructor to    */
/* create a Streaming (TCP) Socket using either IPv4 or IPv6 as specified,    */
/* the bIsConnected flag is initialised to false.                             */
/******************************************************************************/
TCPSocket::TCPSocket(bool bUseIPv6 = false)
         : SocketBase(bUseIPv6, SOCK_STREAM), bLocalSet(false), bIsConnected(false) {

}

/******************************************************************************/
/* void SetLocal()                                                            */
/*                                                                            */
/* This protected method is called by inherited classes when the local socket */
/* address is valid.  It sets the bLocalSet flag, obtains details of the      */
/* local address and stores them in the internal member variables.            */
/******************************************************************************/
void TCPSocket::SetLocal() {
    GetSockName(cLocalAddress, iLocalPort);
    bLocalSet = true;
}

/******************************************************************************/
/* void SetConnected()                                                        */
/*                                                                            */
/* This protected method is called by inherited classes when the socket has   */
/* been connected.  It sets the bIsConnected flag, obtains details of the     */
/* remote connection and stores them in the internal member variables.        */
/******************************************************************************/
void TCPSocket::SetConnected() {
    GetPeerName(cRemoteAddress, iRemotePort);
    bIsConnected = true;
}

/******************************************************************************/
/* void SendData(char *pData, int iDataLen)                                   */
/*                                                                            */
/* This method is called to send a block of data to the remote connection.    */
/* The parameters signify the Data Payload and its size.  If the socket is    */
/* not connected, then we throw a NotConnected exception, otherwise we call   */
/* the base class Send() method to send the data, returning the number of     */
/* bytes actually sent.                                                       */
/******************************************************************************/
int TCPSocket::SendData(char *pData, int iDataLen) {
    
    if (!bIsConnected) {
        throw SocketException(SocketException::errNotConnected);
    }
    
    return Send(pData, iDataLen, 0);
}

/******************************************************************************/
/* void RecvData(char *pBuffer, int iBufferLen)                               */
/*                                                                            */
/* This method is called to receive a block of data on the connected socket.  */
/* The parameters signify the payload receiving buffer and its size.  If the  */
/* socket is not connected, then we throw a NotConnected exception, otherwise */
/* we call the base class Recv() method to receive the data, returning the    */
/* number of bytes actually written.                                          */
/******************************************************************************/
int TCPSocket::RecvData(char *pBuffer, int iBufferLen) {
    if (!bIsConnected) 
        throw SocketException(SocketException::errNotConnected);

    return Recv(pBuffer, iBufferLen, 0);
}

/******************************************************************************/
/*int SendCompleteData(char *pData, int iDataLen)                             */
/*                                                                            */
/* SendCompleteData Function use loop to send data to remote socket.          */
/******************************************************************************/
int TCPSocket::SendCompleteData(char *pData, int iDataLen) {
    
    register int r;
    register int nLeft = iDataLen;
    
    while(nLeft > 0) {
        r = SendData(pData, nLeft);
        
        if(r == 0)
            break;
        
        nLeft -= r;
        pData += r;
    }
    
    return iDataLen;
}

/******************************************************************************/
/*int RecvCompleteData(char *pBuffer, int iBufferLen)                         */
/*                                                                            */
/* RecvCompleteData Function use loop to recv data from remote socket.        */
/******************************************************************************/
int TCPSocket::RecvCompleteData(char *pBuffer, int iBufferLen) {
    
    register int r;
    register int nLeft = iBufferLen;

    while (nLeft > 0) {
	    r = RecvData(pBuffer, nLeft);

        if (r == 0)
            throw SocketException(SocketException::errNotConnected);
            
	    nLeft -= r;
	    pBuffer += r;
    }
    
    return iBufferLen - nLeft;
    
}

/******************************************************************************/
/*void WriteJsonToSocket(cJSON *json)                                         */
/*                                                                            */
/* Send json object to socket.                                                */
/******************************************************************************/
void TCPSocket::WriteJsonToSocket(cJSON *json) {
    
    int hSize, nSize;
    char *str;
    
    str = cJSON_PrintUnformatted(json);
    if (str == NULL)
        throw SocketException(SocketException::errMemory);
    else {
        hSize = strlen(str);
        nSize = htonl(hSize);
        
        SendCompleteData((char*) &nSize, sizeof(nSize));
        SendCompleteData(str, hSize);

        free(str);
    }
}

/******************************************************************************/
/*cJSON * ReadJsonFromSocket()                                                */
/*                                                                            */
/* Recv json object from socket.                                              */
/******************************************************************************/
cJSON * TCPSocket::ReadJsonFromSocket() {
    int hSize, nSize;
    char *str;
    cJSON *jsonObj = NULL;
    
    RecvCompleteData((char*) &nSize, sizeof(nSize));
    hSize = ntohl(nSize);
    
    str = (char *) malloc(hSize+1);
    if(str != NULL) {
        RecvCompleteData(str, hSize);
        str[hSize] = '\0';
        jsonObj = cJSON_Parse(str);
    }
    free(str);
    
    return jsonObj;
}

/******************************************************************************/
/* IPAddress & LocalIPAddress()                                               */
/*                                                                            */
/* This method returns the local IP Address of the socket connection.  If the */
/* address has not been set (socket not bound or connection not accepted), we */
/* throw a NotBound exception, otherwise we return a copy of cLocalAddress.   */
/******************************************************************************/
IPAddress & TCPSocket::LocalIPAddress() {
    if (!bLocalSet) {
        throw SocketException(SocketException::errNotBound);
    }
    
    return cLocalAddress;
}

/* Return TCPSocket local hostname. */
char* TCPSocket::LocalHostName() {
    return (char *)cLocalAddress;
}

/******************************************************************************/
/* int LocalPortNumber()                                                      */
/*                                                                            */
/* This method returns the local port number of the socket connection.  If    */
/* the port number has not been set (socket not bound or connection not       */
/* accepted), we throw a NotBound exception, otherwise we return iLocalPort.  */
/******************************************************************************/
int TCPSocket::LocalPortNumber() {
    if (!bLocalSet) {
        throw SocketException(SocketException::errNotBound);
    }
    
    return iLocalPort;
}

/******************************************************************************/
/* IPAddress & RemoteIPAddress()                                              */
/*                                                                            */
/* This method returns the remote IP Address of the socket connection.  If    */
/* the socket is not connected, there is no remote IP Address and we throw a  */
/* NotBound exception, otherwise we return a copy of cRemoteAddress.          */
/******************************************************************************/
IPAddress & TCPSocket::RemoteIPAddress() {
    if (!bIsConnected) {
        throw SocketException(SocketException::errNotConnected);
    }
    
    return cRemoteAddress;
}

/* Return TCPSocket remote hostname. */
char* TCPSocket::RemoteHostName() {
    return (char *)cRemoteAddress;
}

/******************************************************************************/
/* int RemotePortNumber()                                                     */
/*                                                                            */
/* This method returns the remote port number of the socket connection.  If   */
/* the socket is not connected, there is no remote port number and we throw a */
/* NotBound exception, otherwise we return iRemotePort.                       */
/******************************************************************************/
int TCPSocket::RemotePortNumber() {
    if (!bIsConnected) {
        throw SocketException(SocketException::errNotConnected);
    }
    
    return iRemotePort;
}

/******************************************************************************/
/* void GetSockOpt(int iCodeLevel, int iOptionName, void *pOptionData,        */
/*                 int &iDataLength)                                          */
/*                                                                            */
/* This method is a wrapper method for the protected base class GetSockOpt()  */
/* method.                                                                    */
/******************************************************************************/
void TCPSocket::GetSockOpt(int iCodeLevel, int iOptionName, void *pOptionData, int &iDataLength) {
    SocketBase::GetSockOpt(iCodeLevel, iOptionName, pOptionData, iDataLength);
}

/******************************************************************************/
/* void SetSockOpt(int iCodeLevel, int iOptionName, const void *pOptionData,  */
/*                 int iDataLength)                                           */
/*                                                                            */
/* This method is a wrapper method for the protected base class SetSockOpt()  */
/* method.                                                                    */
/******************************************************************************/
void TCPSocket::SetSockOpt(int iCodeLevel, int iOptionName, const void *pOptionData, int iDataLength) {
    SocketBase::SetSockOpt(iCodeLevel, iOptionName, pOptionData, iDataLength);
}

/******************************************************************************/
/* class TCPClientSocket.                                                     */
/******************************************************************************/
/* TCPClientSocket Constructor.                                               */
/*                                                                            */
/* The default socket type is IPv4.  We call the base class constructor to    */
/* create a Streaming (TCP) Socket using either IPv4 or IPv6 as specified.    */
/******************************************************************************/
TCPClientSocket::TCPClientSocket(bool bUseIPv6 = false)
               : TCPSocket(bUseIPv6) {
}

/******************************************************************************/
/* TCPClientSocket Constructor.                                               */
/*                                                                            */
/* The default socket type is IPv4.  We call the base class constructor to    */
/* create a Streaming (TCP) Socket using either IPv4 or IPv6 as specified.    */
/* We then call the ConnectServer() method to connect to the specified IP     */
/* address and port number.  This call will set all local and remote address  */
/* details as well as flags enabling further transmission of data.  If the    */
/* connect fails, a suitable exception will be thrown.                        */
/******************************************************************************/
TCPClientSocket::TCPClientSocket(IPAddress &cServAddr, int iServPort, 
                                 bool bUseIPv6 = false)
               : TCPSocket(bUseIPv6) {
    ConnectServer(cServAddr, iServPort);
}

/******************************************************************************/
/* void ConnectServer(IPAddress &cServAddr, int iServPort)                    */
/*                                                                            */
/* This method is called to connect the socket to a server TCP socket that is */
/* specified by the provided IP Address and port number.  We call the base    */
/* class Connect() method to perform the connection, this throws an exception */
/* if the connect fails.  After connecting, we call the protected methods     */
/* SetLocal() and SetConnected() to obtain the local and remote address       */
/* details of the connection as well as set flags to enable later retrieval   */
/* of these values and transmission of data.                                  */
/******************************************************************************/
void TCPClientSocket::ConnectServer(IPAddress &cServAddr, int iServPort) {
    Connect(cServAddr, iServPort);

    SetLocal();
    SetConnected();
}

/******************************************************************************/
/* class TCPServerSocket.                                                     */
/******************************************************************************/
/* TCPServerSocket Constructor.                                               */
/*                                                                            */
/* The default local port number is automatically allocated, the default back */
/* logged queue length is 1 and the default socket type is IPv4.  The socket  */
/* is bound to the specified IP Address on the host and we set the socket to  */
/* begin listening.  We call the base class constructor to SocketBase using   */
/* either IPv4 or IPv6 as specified, a streaming socket (TCP) is default.  We */
/* then try to bind the TCP Socket to the specified IP Address and port       */
/* number using the base class Bind() method.  Following this, we call the    */
/* base class GetSockName() method to obtain the actual IP Address and port   */
/* number allocated to the socket and the base class Listen() method to cause */
/* the socket to begin listening for new connections.  If any of these calls  */
/* fail, we catch and re-throw any exception.                                 */
/******************************************************************************/
TCPServerSocket::TCPServerSocket(IPAddress &cLocalAddr, int iLocalPort = 0, 
                                 int iBackLog = 1, bool bUseIPv6 = false)
               : TCPSocket(bUseIPv6) {
    
    const int iReUseAddrFlag = 1;

    SetSockOpt(SOL_SOCKET, SO_REUSEADDR, &iReUseAddrFlag, sizeof(iReUseAddrFlag));
    Bind(cLocalAddr, iLocalPort);
    SetLocal();
    Listen(iBackLog);
}

/******************************************************************************/
/* TCPServerSocket Constructor.                                               */
/*                                                                            */
/* The default local port number is automatically allocated, the default back */
/* logged queue length is 1 and the default socket type is IPv4.  We call the */
/* base class constructor to SocketBase using either IPv4 or IPv6 as          */
/* specified, a streaming socket (TCP) is default.  We then try to bind the   */
/* Socket to the specified port number and any local IP Address using the     */
/* base class Bind() method.  Following this, we call the base class          */
/* GetSockName() method to obtain the actual IP Address and port number       */
/* allocated to the socket and the base class Listen() method to cause the    */
/* socket to begin listening for new connections.  If any of these calls fail,*/
/* we catch and re-throw any exception.                                       */
/******************************************************************************/
TCPServerSocket::TCPServerSocket(int iLocalPort = 0, int iBackLog = 1,
                                 bool bUseIPv6 = false)
               : TCPSocket(bUseIPv6) {
    
    const int iReUseAddrFlag = 1;

    SetSockOpt(SOL_SOCKET, SO_REUSEADDR, &iReUseAddrFlag, sizeof(iReUseAddrFlag));
    Bind(iLocalPort);
    SetLocal();
    Listen(iBackLog);
}

TCPServerSocket::TCPServerSocket(bool bUseIPv6) 
               : TCPSocket(bUseIPv6){

}

/******************************************************************************/
/* TCPSocket *AcceptClient()                                                  */
/*                                                                            */
/* This method is called to accept a new connection on the server socket.     */
/* Further communications on the newly connected socket are made via the      */
/* newly created TCPSocket instance of which a pointer is returned when the   */
/* connection is accepted.  We create a TCPSocket instance and pass this to   */
/* the base class Accept() method to accept the pending connection on this    */
/* TCPSocket instance.  Once this has occured, we call the TCPSocket methods  */
/* SetLocal() and SetConnected(), this allows the newly connected socket to   */
/* learn its local and remote addresses, as well as being able to send and    */
/* receive data.                                                              */
/******************************************************************************/
TCPSocket * TCPServerSocket::AcceptClient() {
    IPAddress   cClientAddress;
    int         iClientPort;
    TCPSocket   *pcNewSocket = new TCPSocket;

    Accept((SocketBase *)pcNewSocket, cClientAddress, iClientPort);
    pcNewSocket->SetLocal();
    pcNewSocket->SetConnected();

    return pcNewSocket;
}

/******************************************************************************/
/* End of File: tcpSocket.cpp                                                */
/******************************************************************************/
